package com.huawei.bitfun.insight;

import com.huawei.bitfun.insight.utils.Common;
import org.cef.callback.CefCallback;
import org.cef.handler.CefResourceHandlerAdapter;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefRequest;
import org.cef.network.CefResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

public class InsightExResourceHandler extends CefResourceHandlerAdapter {
    private static final Logger LOGGER = LoggerFactory.getLogger(InsightExResourceHandler.class);

    private static final Map<String, String> EXT_2_TYPE = Map.of(
            "css", "text/css",
            "js", "text/javascript",
            "html", "text/html",
            "png", "image/png",
            "woff2", "application/octet-stream",
            "wasm", "application/wasm",
            "mjs", "text/javascript",
            "map", "application/json");
    protected final String requestUrl;
    protected final String requestPath;
    protected InputStream inputStream;
    protected URLConnection connection;

    public InsightExResourceHandler(String requestUrl) {
        this.requestUrl = requestUrl;
        int paramIndex = requestUrl.indexOf("?");
        if (paramIndex != -1) {
            requestPath = requestUrl.substring(0, paramIndex);
        } else {
            requestPath = requestUrl;
        }
    }


    @Override
    public boolean processRequest(CefRequest request, CefCallback callback) {
        if (!requestUrl.equals(request.getURL())) {
            throw new IllegalStateException();
        }
        if (requestPath == null || requestPath.length() < Common.URL_PREFIX.length()) {
            return false;
        }
        String transformedUrl = requestPath.substring(Common.URL_PREFIX.length())
                .replaceFirst(Common.DOMAIN_NAME, Common.FRONTEND_DIR);
        URL resourceUrl = this.getClass().getClassLoader().getResource(transformedUrl);
        if (resourceUrl == null) {
            LOGGER.warn("Failed to find resource: {}", transformedUrl);
            return false;
        }
        try {
            this.connection = resourceUrl.openConnection();
            this.inputStream = this.connection.getInputStream();
        } catch (IOException e) {
            LOGGER.warn("Failed to open resource: {}", transformedUrl);
            return false;
        }
        callback.Continue();
        return true;
    }

    @Override
    public void getResponseHeaders(CefResponse response, IntRef responseLength, StringRef redirectUrl) {
        if (inputStream == null) {
            response.setStatus(404);
            return;
        }
        for (var entry : EXT_2_TYPE.entrySet()) {
            if (requestPath.endsWith(entry.getKey())) {
                response.setMimeType(entry.getValue());
                break;
            }
        }
        if (connection != null) {
            responseLength.set(connection.getContentLength());
        } else {
            responseLength.set(0);
        }
        Map<String, String> headers = new HashMap<>();
        response.getHeaderMap(headers);
        headers.put("Cross-Origin-Opener-Policy", "same-origin");
        headers.put("Cross-Origin-Embedder-Policy", "require-crop");
        headers.remove("Access-Control-Allow-Origin");
        headers.remove("Access-Control-Allow-Methods");
        response.setHeaderMap(headers);
        response.setStatus(200);
    }

    @Override
    public boolean readResponse(byte[] dataOut, int bytesToRead, IntRef bytesRead, CefCallback callback) {
        try {
            int availableSize = inputStream.available();
            if (availableSize > 0) {
                int maxBytesToRead = Math.min(bytesToRead, availableSize);
                int actualBytesRead = inputStream.read(dataOut, 0, maxBytesToRead);
                bytesRead.set(actualBytesRead);
                return true;
            } else {
                inputStream.close();
                return false;
            }
        } catch (IOException e) {
            LOGGER.warn("Failed to read response", e);
        }
        bytesRead.set(0);
        try {
            inputStream.close();
        } catch (IOException e) {
            LOGGER.warn("Failed to close input stream", e);
        }
        return false;
    }

    @Override
    public void cancel() {
        try {
            inputStream.close();
        } catch (IOException e) {
            LOGGER.warn("Failed to close input stream", e);
        }
    }
}
